from typing import OrderedDict
import pyvisa
import enum
import hashlib
import logging


class FlukeResolution(enum.Enum):
    MIN = 'MIN'
    MAX = 'MAX'
    DEF = 'DEF'


class Fluke8842_GPIB():
    
    TRUE_STRINGS= {'TRUE', 'YES', 'ON', '1'}
    FALSE_STRINGS = {'FALSE', 'NO', 'OFF', '0', 'NONE', ''}

    FLUKE_TIMEOUT = 1000

    def __init__(self, resourceName, *args, **kwargs):
        self.resourceName = resourceName
        self.inst = self._fluke_connection(visa_resource_name=self.resourceName)
        self.fluke_initialize()

    def _fluke_connection(self, visa_resource_name):
        logging.debug('fluke_connection: %s' % visa_resource_name)
        rm = pyvisa.ResourceManager()
        resource_list = rm.list_resources(query='?*::INSTR')
        logging.info('list_resources:', resource_list)
        if visa_resource_name in resource_list:
            inst = rm.open_resource(visa_resource_name)
            inst.clear()
            inst.timeout = Fluke8842_GPIB.FLUKE_TIMEOUT
            return inst
        else:
            raise AssertionError('This fluke is not online.')

    def fluke_initialize(self, reset=True):
        logging.debug('fluke_initialize...')
        self.send_raw_command('*IDN?')
        data = self._fluke_read()
        if 'FLUKE' in data.upper():
            if reset is True:
                self._fluke_reset()
            else:
                pass
        else:
            AssertionError('Fluke initialize failed: fluke general information is wrong. The return content is: {}'.format(data))
    
    def fluke_close(self):
        self._fluke_disconnection()


    def _to_enum(self, name, enumClass):
        if not issubclass(enumClass, enum.Enum):
            raise IOError('Class {:s} is not a subclass of {:s}'.format(enumClass.__name__, enum.Emum.__name__))

        if issubclass(name.__class__, enum.Enum) and name.__class__ == enumClass:
            return name

        elif isinstance(name, str):
            try:
                return enumClass[name]
            except KeyError:
                raise KeyError('Enum class {:s} does not have an item with name {:s}'.format(enumClass.__name__, name))
        else:
            try:
                return enumClass(name)
            except:
                raise ValueError('Object of type {:s} is not a valid {:s} enum item'.format(type(name).__name__, enumClass.__name__))
    
    def _is_truthy(self, item):
        if isinstance(item, str):
            return item.upper not in self.FALSE_STRINGS
        return bool(item)



    def _fluke_disconnection(self):
        logging.debug('fluke_disconnection')
        self.inst.close()

    def _fluke_read(self):
        try:
            content = self.inst.read()
        except Exception as err:
            self._fluke_disconnection()
            raise IOError('Read error:{}'.format('err'))
        return content

    def _fluke_query(self, command):
        data_length = len(command)
        if data_length != 0:
            try:
                content = self.inst.query(command)
            except Exception as err:
                self._fluke_disconnection()
                raise IOError('Query command ({:s}) to fluke is failed:{:s}'.format(command, err))
        return content

    
    def _fluke_reset(self):
        #self.send_raw_command('*CLS');
        #self.send_raw_command('*RST');
        self.fluke_error_query()

    def fluke_error_query(self):
        inst_errors = []
        error_ret = self.query_command(command=':SYST:ERR?')
        error_list = error_ret.split(',')
        error_num = error_list[0]
        if error_num.startswith('+'):
            error_num = error_num[1:]
        if int(error_num) == 0:
            logging.info('+0, No Error')
        else:
            while int(error_num) != 0:
                logging.info('Error #: %s, %s' % (error_list[0], error_list[1]))
                inst_errors.append(error_list[0])
                inst_errors.append(error_list[1])
                error_list = self.query_command(command=':SYST:ERR?')
                error_no = error_list[0]
        return inst_errors

    def send_raw_command(self, command, *args):
        logging.info('send_raw_command:%s,%s' % (command, args))
        if not command:
            raise ValueError('Command string is empty')
        
        try:
            execStatus = self.inst.write(command)
            return execStatus
        except:
            self._fluke_disconnection()
            raise IOError('Send command ({:s}) to fluke is failed.'.format(command))

    def query_command(self, command, *args):
        logging.info('querysend_command:%s,%s' % (command, args))
        ret = self.inst.query(command)
        return ret


    def read_data(self):
        r = self._fluke_read()
        return r

    # region Device Locking
    def lock_device(self, cleanStart=True):
        if self._locked:
            raise IOError('Device {:s} already locked'.format(self.resourceName))
        self._fluke_connection(visa_resource_name=self.resourceName)
        self._locked = True
        if cleanStart:
            pass

    def unlock_device(self):
        if not self._locked:
            raise IOError('Not locked any device')
        self._fluke_disconnection()
        self._locked = False

    @staticmethod
    def get_connectionID(resourceName, *args, **kwargs):
        return hashlib.sha1(str(resourceName).lower().encode('utf-8').hexdigest())

    
    # endregion

    # regison Aux Output
    def activate_aux_output(self, u, auxId=1):
        raise NotImplementedError()

    def deactivate_aux_output(self, auxID=1):
        raise NotImplementedError()

    # endregion

    # region fluke setting
    def fluke_configure_global_setting(self, freque):
        raise NotImplementedError()

    def error_check(self):
        myError = []
        ErrorList = self.inst.query("SYST:ERR?").split(',')
        Error = ErrorList[0]
        if int(Error) == 0:
            print ("+0, No Error!")
        else:
            while int(Error)!=0:
                print ("Error #: " + ErrorList[0])
                print ("Error Description: " + ErrorList[1])
                myError.append(ErrorList[0])
                myError.append(ErrorList[1])
                ErrorList = self.inst.query("SYST:ERR?").split(',')
                Error = ErrorList[0]
                myError = list(myError)
        return myError

if __name__ == '__main__':
    
    logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')


    device = Fluke8842_GPIB('GPIB0::1::INSTR')
    device.send_raw_command('*IDN?')







